import logging

from sqlalchemy import and_, desc, select
from sqlalchemy.exc import DBAPIError, IntegrityError
from sqlalchemy.orm import joinedload

from poker_bot.core.exceptions.common import RepoError
from poker_bot.core.interfaces import DepositRepo
from poker_bot.core.interfaces.deposit import DepositReader
from poker_bot.core.models.dto.deposit import DepositWithDetailsDTO
from poker_bot.core.models.enums.deposit import DepositStatus
from poker_bot.database.converters.deposit import (
    convert_db_model_to_deposit_entity,
    convert_db_model_to_deposit_with_details_dto,
    convert_deposit_entity_to_db_model,
)
from poker_bot.database.exception_mapper import exception_mapper
from poker_bot.database.models import Deposit, User
from poker_bot.database.repositories.base import SQLAlchemyRepo
from poker_bot.domain.deposit import DepositEntity

logger = logging.getLogger(__name__)


class DepositReaderImpl(SQLAlchemyRepo, DepositReader):
    @exception_mapper
    async def get_deposit_by_id(self, deposit_id: int) -> DepositWithDetailsDTO | None:
        """Получаем депозит по id"""

        query = (
            select(Deposit)
            .where(Deposit.id == deposit_id)
            .options(joinedload(Deposit.payment_method))
        )
        deposit: Deposit | None = await self._session.scalar(query)

        if not deposit:
            return None

        return convert_db_model_to_deposit_with_details_dto(deposit, True)

    @exception_mapper
    async def get_all_deposits(self) -> list[DepositWithDetailsDTO]:
        query = select(Deposit).options(joinedload(Deposit.payment_method))

        res = await self._session.scalars(query)
        deposits: list[Deposit] = list(res.unique())

        return [
            convert_db_model_to_deposit_with_details_dto(deposit, True)
            for deposit in deposits
        ]

    @exception_mapper
    async def get_user_deposits(self, user_tg_id: int) -> list[DepositWithDetailsDTO]:
        """Получаем депозиты пользователя"""

        query = (
            select(Deposit)
            .outerjoin(Deposit.user)
            .where(User.telegram_id == user_tg_id)
            .order_by(desc(Deposit.created_at))
            .options(joinedload(Deposit.payment_method))
        )

        res = await self._session.scalars(query)
        deposits: list[Deposit] = list(res.unique())

        return [
            convert_db_model_to_deposit_with_details_dto(deposit, True)
            for deposit in deposits
        ]

    @exception_mapper
    async def get_deposits_for_confirmation(self) -> list[DepositWithDetailsDTO]:
        query = (
            select(Deposit)
            .where(
                and_(
                    Deposit.status == DepositStatus.waiting_payment,
                    Deposit.invoice_screenshot_path != None,
                )
            )
            .order_by(desc(Deposit.created_at))
            .options(joinedload(Deposit.payment_method))
        )

        res = await self._session.scalars(query)
        deposits: list[Deposit] = list(res.unique())

        return [
            convert_db_model_to_deposit_with_details_dto(deposit, True)
            for deposit in deposits
        ]


class DepositRepoImpl(SQLAlchemyRepo, DepositRepo):
    @exception_mapper
    async def acquire_deposit_by_id(self, deposit_id: int) -> DepositEntity | None:
        """Получаем заказ по id"""

        query = (
            select(Deposit)
            .where(Deposit.id == deposit_id)
            .options(joinedload(Deposit.payment_method))
        )
        deposit: Deposit | None = await self._session.scalar(query)

        return convert_db_model_to_deposit_entity(deposit)

    @exception_mapper
    async def create_deposit(self, deposit: DepositEntity) -> DepositEntity:
        db_deposit = convert_deposit_entity_to_db_model(deposit)
        self._session.add(db_deposit)
        try:
            await self._session.flush((db_deposit,))
        except IntegrityError as err:
            self._parse_error(err)

        return convert_db_model_to_deposit_entity(db_deposit)

    @exception_mapper
    async def update_deposit(self, deposit: DepositEntity) -> None:
        db_deposit = convert_deposit_entity_to_db_model(deposit)
        try:
            await self._session.merge(db_deposit)
        except IntegrityError as err:
            self._parse_error(err)

    @staticmethod
    def _parse_error(err: DBAPIError) -> None:
        raise RepoError
